package pl.edu.agh.ki.neuralnetwork.builder;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import pl.edu.agh.ki.neuralnetwork.exceptions.NeuronAlreadyConnectedException;
import pl.edu.agh.ki.neuralnetwork.exceptions.NotEnoughLayersException;
import pl.edu.agh.ki.neuralnetwork.exceptions.NotEnoughNeuronsException;
import pl.edu.agh.ki.neuralnetwork.layer.Layer;
import pl.edu.agh.ki.neuralnetwork.layer.SimpleLayer;
import pl.edu.agh.ki.neuralnetwork.network.NeuralNetwork;
import pl.edu.agh.ki.neuralnetwork.network.NeuralNetworkImpl;
import pl.edu.agh.ki.neuralnetwork.neurons.InnerNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.InputNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.InputNeuronImpl;
import pl.edu.agh.ki.neuralnetwork.neurons.Neuron;
import pl.edu.agh.ki.neuralnetwork.neurons.ThresholdNeuron;

/**
 * Builder that creates a random network consisting of Threshold Neurons
 * @author ravn
 *
 */
public class SimpleThresholdNetworkBuilder implements NeuralNetworkBuilder {

    private final static double low = 0.1;
    private final static double high = 0.9;

    private Random random = new Random();
    private Integer layers[];

    /**
     *
     * @param layers Table containing number of neurons in layers
     */
    public SimpleThresholdNetworkBuilder(Integer layers[]) throws NotEnoughLayersException, NotEnoughNeuronsException {
        if(layers.length < 1)
            throw new NotEnoughLayersException();
        for(Integer count : layers)
            if(count < 1)
                throw new NotEnoughNeuronsException();
        this.layers = layers;
    }

    public NeuralNetwork build() {
        Layer<InputNeuron> inputNeurons = new SimpleLayer<InputNeuron>();
        List<Layer<InnerNeuron> > innerLayers = new LinkedList<Layer<InnerNeuron> >();
        for(int i=0; i<layers[0]; i++) {
            inputNeurons.add(new InputNeuronImpl());
        }
        for(int i=1; i<layers.length; i++) {
            Layer<InnerNeuron> layer = new SimpleLayer<InnerNeuron>();
            for(int j=0; j<layers[i]; j++) {
                try {
                    double threshold = random.nextDouble();
                    if (threshold < low || threshold > high) {
                        threshold = 0.5;
                    }
                    InnerNeuron neuron = new ThresholdNeuron(threshold);
                    Layer<? extends Neuron> previousLayer = i==1 ? inputNeurons : innerLayers.get(innerLayers.size()-1);
                    for (Neuron input : previousLayer) {
                        neuron.addInput(input, random.nextDouble());
                    }
                    layer.add(neuron);
                } catch (NeuronAlreadyConnectedException ex) {
                    Logger.getLogger(SimpleThresholdNetworkBuilder.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            innerLayers.add(layer);
        }
        NeuralNetwork network = null;
        try {
            network = new NeuralNetworkImpl(inputNeurons, innerLayers);
        } catch (NotEnoughLayersException ex) {
            Logger.getLogger(SimpleThresholdNetworkBuilder.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NotEnoughNeuronsException ex) {
            Logger.getLogger(SimpleThresholdNetworkBuilder.class.getName()).log(Level.SEVERE, null, ex);
        }
        return network;
    }

}